/*
 * Copyright (C) 2012 SeekDeepWithin.com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package com.seekdeepwithin.Controls;

import com.almworks.sqlite4java.*;
import com.seekdeepwithin.*;
import com.seekdeepwithin.Data.*;
import com.seekdeepwithin.Data.ReadLink;
import com.seekdeepwithin.Data.ITab;
import com.seekdeepwithin.Data.Book;
import com.seekdeepwithin.Data.Passage;
import com.seekdeepwithin.DataAccess.*;
import java.io.*;
import java.net.*;
import java.text.*;
import java.util.*;
import javafx.beans.property.*;
import javafx.beans.value.*;
import javafx.fxml.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;

/**
 * Reader Controller class
 *
 * @author Jonathan Montiverdi Date: Oct 3, 2012
 */
public class ReaderControl extends BorderPane implements ITab, ChangeListener<TreeItem<ReadLink>>, IWebEventHandler
{
   @FXML private TreeView<ReadLink> _ContentTree;
   @FXML private WebControl _WebControl;
   private final Book m_Book;
   private BaseDbObject m_Header;
   private BaseDbObject m_Footer;
   private ReadLink m_CurrentLink;
   private ArrayList<Passage> m_Passages;
   private StringProperty tabTitle = new SimpleStringProperty ();
   private ObjectProperty<Tooltip> tabToolTip = new SimpleObjectProperty<> ();

   /**
    * Initializes a new library control.
    */
   @SuppressWarnings ("LeakingThisInConstructor")
   public ReaderControl (Book book)
   {
      this.m_Book = book;
      this.tabTitle.set (book.getTitle ());
      this.tabToolTip.set (new Tooltip (book.getTitle ()));
      this.m_Passages = new ArrayList<> ();
      FXMLLoader fxmlLoader = new FXMLLoader (getClass ().getResource ("Reader.fxml"));
      fxmlLoader.setRoot (this);
      fxmlLoader.setController (this);
      try {
         fxmlLoader.load ();
      } catch (IOException ex) {
         throw new RuntimeException (ex);
      }
      // TODO: Mulitple select...
      this._ContentTree.getSelectionModel ().setSelectionMode (SelectionMode.SINGLE);
      this._ContentTree.getSelectionModel ().selectedItemProperty ().addListener (this);
      this._ContentTree.getSelectionModel ().select (0);
      this._WebControl.setHandler (this);
      this.loadContents ();
   }

   /**
    * Loads the table of contents.
    */
   private void loadContents ()
   {
      TreeItem<ReadLink> root = new TreeItem<> (new ReadLink (""));

      try {
         SQLiteStatement statement = SdwDatabase.getInstance ().prepare (
                 "SELECT subbook_list.sub_book_list_id, books.title"
                 + " FROM subbook_list"
                 + " INNER JOIN books ON subbook_list.sub_book_id = books.book_id"
                 + " WHERE subbook_list.book_id = ?"
                 + " ORDER BY subbook_list.order_index;");
         statement.bind (1, this.m_Book.getId ());
         while (statement.step ()) {
            String subBookTitle = statement.columnString (1);
            TreeItem<ReadLink> subBookTreeItem = null;
            if (!subBookTitle.equalsIgnoreCase ("default")) {
               ReadLink subBookLink = new ReadLink (this.m_Book.getTitle () + ":" + subBookTitle + ":About:");
               subBookLink.setText (subBookTitle);
               subBookTreeItem = new TreeItem<> (subBookLink);
               root.getChildren ().add (subBookTreeItem);
            }

            // TODO: CHECK FOR LINKED SUBBOOK...
            SQLiteStatement chapStatement = SdwDatabase.getInstance ().prepare (
                    "SELECT chapters.name"
                    + " FROM chapter_list"
                    + " INNER JOIN chapters ON chapter_list.chapter_id=chapters.chapter_id"
                    + " WHERE chapter_list.sub_book_list_id = ?"
                    + " ORDER BY chapter_list.order_index;");
            chapStatement.bind (1, statement.columnInt (0));
            while (chapStatement.step ()) {
               String chapTitle = chapStatement.columnString (0);
               ReadLink chapLink = new ReadLink (this.m_Book.getTitle () + ":" + subBookTitle + ":" + chapTitle + ":");
               chapLink.setText (chapTitle.equalsIgnoreCase ("default") ? "Read"
                                 : chapTitle);
               TreeItem<ReadLink> chapterTreeItem = new TreeItem<> (chapLink);
               if (subBookTreeItem == null) {
                  root.getChildren ().add (chapterTreeItem);
               } else {
                  subBookTreeItem.getChildren ().add (chapterTreeItem);
               }
            }
            chapStatement.dispose ();
         }
         statement.dispose ();
      } catch (SQLiteException ex) {
         Dialogs.showErrorDialog (MainController.getInstance ().getStage (),
                                  "An error occured while loading the book's table of contents from the main database.",
                                  "Database error - ReaderControl.loadContents ()", "Error", ex);
      }

      this._ContentTree.setRoot (root);
   }

   /**
    * Gets the title of the tab.
    * @return The title of the tab.
    */
   @Override
   public String getTabTitle ()
   {
      return this.tabTitle.get ();
   }

   /**
    * Gets the title of the tab property.
    * @return Tab title property.
    */
   @Override
   public StringProperty tabTitleProperty ()
   {
      return this.tabTitle;
   }

   /**
    * Gets the tool tip of the tab.
    * @return The tool tip of the tab.
    */
   @Override
   public Tooltip getTabToolTip ()
   {
      return this.tabToolTip.get ();
   }

   /**
    * Gets the tool tip of the tab property.
    * @return Tab tool tip property.
    */
   @Override
   public ObjectProperty<Tooltip> tabToolTipProperty ()
   {
      return this.tabToolTip;
   }

   /**
    * Opens an internal wiki link.
    * @param link ReadLink to open.
    */
   @Override
   public void openLink (String link)
   {
      //this.read (new ReadLink (link));
   }

   /**
    * Opens an external wiki link.
    * @param link ReadLink to open.
    */
   @Override
   public void openExternalLink (String link)
   {
   }

   /**
    * Places the data with the given key on the clipboard.
    * @param subBook Sub book data is in.
    * @param chapter Chapter data is in.
    * @param verse Verse number of the data being copied.
    */
   @Override
   public void copy (String subBook, String chapter, String verse)
   {
      /*Passage passage = null;
       for (Passage e : this.m_Passages) {
       if ((!subBook.isEmpty () && e.getSubBook ().equalsIgnoreCase (subBook))
       && e.getChapter ().equals (chapter)
       && e.getVerse ().equals (verse)) {
       passage = e;
       break;
       }
       }

       if (passage == null) {
       return;
       }
       ClipboardContent content = new ClipboardContent ();
       content.putString (((subBook.isEmpty ()) ? passage.getBook () : subBook)
       + " " + chapter + ":" + verse + " - "
       + Html.StripHtml (passage.getPassage ()));
       Clipboard.getSystemClipboard ().setContent (content);*/
   }

   /**
    * Occurs when the selection in the content tree has changed.
    * @param ov
    * @param previousSelection The previously selected tree item.
    * @param currentSelection The currently selected tree item.
    */
   @Override
   public void changed (ObservableValue<? extends TreeItem<ReadLink>> ov, TreeItem<ReadLink> previousSelection, TreeItem<ReadLink> currentSelection)
   {
      this.read (currentSelection.getValue ());
   }

   /**
    * Opens the given link.
    * @param link ReadLink to open.
    */
   public void read (ReadLink link)
   {
      if (link.getFullLink ().isEmpty ()) {
         // Do nothing, this is probably a folder...
      } else if (link.hasBook () && !link.getBook ().equals (this.m_Book.getTitle ())) {
         // TODO: Open a different book/page...
      } else {
         // Change the current data...
         this.m_CurrentLink = link;
         this.m_Passages.clear ();
         try {
            int chapterId = SdwDatabase.getInstance ().getChapterId (this.m_Book.getId (), link.getSubBook (), link.getChapter ());

            SQLiteStatement statement = SdwDatabase.getInstance ().prepare (
                    "SELECT headers.header_id, headers.header"
                    + " FROM chapter_headers"
                    + " INNER JOIN headers ON chapter_headers.header_id = headers.header_id"
                    + " WHERE chapter_headers.chapter_list_id=?");
            statement.bind (1, chapterId);
            if (statement.step ()) {
               this.m_Header = new BaseDbObject (statement.columnInt (0), statement.columnString (1));
            } else {
               this.m_Header = null;
            }
            statement.dispose ();

            statement = SdwDatabase.getInstance ().prepare (
                    "SELECT footers.footer_id, footers.footer"
                    + " FROM chapter_footers"
                    + " INNER JOIN footers ON chapter_footers.footer_id = footers.footer_id"
                    + " WHERE chapter_footers.chapter_list_id=?");
            statement.bind (1, chapterId);
            if (statement.step ()) {
               this.m_Footer = new BaseDbObject (statement.columnInt (0), statement.columnString (1));
            } else {
               this.m_Footer = null;
            }
            statement.dispose ();

            statement = SdwDatabase.getInstance ().prepare (
                    "SELECT passage_list.passage_id, passage_list.order_index, passage_list.passage_number, passages.passage"
                    + " FROM passage_list"
                    + " INNER JOIN passages ON passage_list.passage_id = passages.passage_id"
                    + " WHERE passage_list.chapter_list_id = ?"
                    + " ORDER BY passage_list.order_index;");
            statement.bind (1, chapterId);
            while (statement.step ()) {
               Passage passage = new Passage (statement.columnInt (0),
                                              statement.columnString (3),
                                              statement.columnInt (2),
                                              statement.columnInt (1));
               this.getPassageData (passage);
               this.m_Passages.add (passage);
            }
            statement.dispose ();
            // Render Html..
            this.renderPassages ();
         } catch (SQLiteException ex) {
            Dialogs.showErrorDialog (MainController.getInstance ().getStage (), "An error occured while loading the requested material.",
                                     "Database error - ReaderControl.read ()", "Error", ex);
         }
      }
   }

   /**
    * Displays the passage data in the web control.
    */
   private void renderPassages ()
   {
      PassageRenderer renderer = new PassageRenderer ();
      StringBuilder html = new StringBuilder ();
      if (this.m_Header != null) {
         html.append ("<div id=\"chapterHeader\" class=\"chapHeader\">")
                 .append (this.m_Header.getName ())
                 .append ("</div>");
      }

      html.append ("<table class=\"table table-striped\">");
      for (Passage passage : this.m_Passages) {
         int passageNumber = passage.getNumber ();
         html.append ("<tr><td>")
                 .append (passageNumber == -1 ? "" : passageNumber)
                 .append ("</td><td>")
                 .append (renderer.render (passage))
                 .append ("</td><td>")
                 .append (passageNumber == -1 ? "" : passageNumber)
                 .append ("</td></tr>");
      }
      html.append ("</table>");

      if (this.m_Footer != null) {
         html.append ("<div id=\"chapterFooter\" class=\"chapFooter\">")
                 .append (this.m_Footer.getName ())
                 .append ("</div>");
      }

      String footerTable = renderer.getFooterTable ();
      if (!footerTable.isEmpty ()) {
         html.append (footerTable);
      }

      this._WebControl.setHtml (html.toString ());
      if (this.m_CurrentLink.hasVerse ()) {
         try {
            String chapter = URLEncoder.encode (this.m_CurrentLink.getChapter (), "UTF-8");
            String id = "c" + chapter + "v" + this.m_CurrentLink.getVerse ();
            this._WebControl.executeJS ("$(document.body).animate({'scrollTop':   $('#tr_" + id + "').offset().top}, 1000);");
            this._WebControl.executeJS ("$('#td_l" + id + "').css (\"background-color\", \"\");");
            this._WebControl.executeJS ("$('#td_r" + id + "').css (\"background-color\", \"\");");
            this._WebControl.executeJS ("$('#p_" + id + "').css (\"background-color\", \"\");");
         } catch (UnsupportedEncodingException ex) {
            Dialogs.showErrorDialog (MainController.getInstance ().getStage (),
                                     "An error occured while encoding the book selected chapter.",
                                     "Encoding error - ReaderControl.read ()", "Error", ex);
         }
      }
   }

   /**
    * Gets the data for the given passage from the database.
    * @param passage Passage to get data for.
    */
   private void getPassageData (Passage passage)
   {
      try {
         // Header...
         SQLiteStatement statement = SdwDatabase.getInstance ().prepare (
                 "SELECT headers.header_id, headers.header"
                 + " FROM passage_headers"
                 + " INNER JOIN headers ON passage_headers.header_id = headers.header_id"
                 + " WHERE passage_headers.passage_id=?");
         statement.bind (1, passage.getId ());
         if (statement.step ()) {
            passage.setHeader (statement.columnInt (0), statement.columnString (1));
         }
         statement.dispose ();

         // Footers...
         statement = SdwDatabase.getInstance ().prepare (
                 "SELECT footers.footer_id, footers.footer, passage_footers.footer_index"
                 + " FROM passage_footers"
                 + " INNER JOIN footers ON passage_footers.footer_id = footers.footer_id"
                 + " WHERE passage_footers.passage_id=?");
         statement.bind (1, passage.getId ());
         while (statement.step ()) {
            passage.addFooter (statement.columnInt (0), statement.columnString (1), statement.columnInt (2));
         }
         statement.dispose ();

         // Links...
         statement = SdwDatabase.getInstance ().prepare (
                 "SELECT links.link_id, passage_links.start_index, passage_links.end_index, areas.area, links.link"
                 + " FROM passage_links"
                 + " INNER JOIN links ON passage_links.link_id = links.link_id"
                 + " INNER JOIN areas ON links.area_id = areas.area_id"
                 + " WHERE passage_links.passage_id = ?"
                 + " ORDER BY passage_links.passage_id, passage_links.start_index;");
         statement.bind (1, passage.getId ());
         while (statement.step ()) {
            passage.addLink (statement.columnInt (0), statement.columnInt (1), statement.columnInt (2),
                             statement.columnString (3), statement.columnString (4));
         }
         statement.dispose ();

         // Definitions...
         statement = SdwDatabase.getInstance ().prepare (
                 "SELECT glossary_items.glossary_item_id, passage_definitions.start_index, passage_definitions.end_index, glossary_items.glossary_item"
                 + " FROM passage_definitions"
                 + " INNER JOIN glossary_items ON passage_definitions.glossary_item_id = glossary_items.glossary_item_id"
                 //+ " INNER JOIN glossary_list ON glossary_items.glossary_item_id = glossary_list.glossary_item_id"
                 //+ " INNER JOIN glossary_entries ON glossary_list.glossary_entry_id = glossary_entries.glossary_entry_id"
                 + " WHERE passage_definitions.passage_id = ?"
                 + " ORDER BY passage_definitions.passage_id, passage_definitions.start_index;");
         statement.bind (1, passage.getId ());
         while (statement.step ()) {
            passage.addDefinition (statement.columnInt (0), statement.columnInt (1), statement.columnInt (2),
                                   statement.columnString (3));
         }
         statement.dispose ();

         // Styles...
         statement = SdwDatabase.getInstance ().prepare (
                 "SELECT passage_styles.passage_style_id, passage_styles.start_index, passage_styles.end_index,"
                 + " passage_styles.style_id, style_types.style_type_id, styles.style"
                 + " FROM passage_styles"
                 + " INNER JOIN styles ON passage_styles.style_id = styles.style_id"
                 + " INNER JOIN style_types ON styles.style_type_id = style_types.style_type_id"
                 + " WHERE passage_styles.passage_id = ?"
                 + " ORDER BY passage_styles.passage_id, passage_styles.start_index;");
         statement.bind (1, passage.getId ());
         while (statement.step ()) {
            passage.addStyle (statement.columnInt (0), statement.columnInt (1), statement.columnInt (2),
                              statement.columnInt (3), statement.columnInt (4), statement.columnString (5));
         }
         statement.dispose ();
      } catch (SQLiteException ex) {
         String error = "An error occured while getting data for a passage - ID: {0} Number: {1}";
         Dialogs.showErrorDialog (MainController.getInstance ().getStage (),
                                  MessageFormat.format (error, passage.getId (), passage.getNumber ()),
                                  "Database error - ReaderControl.getPassageData ()", "Error", ex);
      }
   }

   /**
    * Occurs when the tab is closing.
    */
   @Override
   public void closing ()
   {
   }
}
